﻿// MIT License Copyright 2014 (c) David Melendez. All rights reserved. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Microsoft.AspNet.Identity;
using Microsoft.WindowsAzure.Storage.Table;
using Microsoft.WindowsAzure.Storage.Table.Queryable;
using Microsoft.WindowsAzure.Storage;
using System.Data.Services.Client;
using System.Data.Services.Common;
using System.Net;
using System.Diagnostics;
using ElCamino.AspNet.Identity.AzureTable.Model;
using ElCamino.AspNet.Identity.AzureTable.Helpers;

namespace ElCamino.AspNet.Identity.AzureTable
{
    public class RoleStore<TRole> : RoleStore<TRole, string, IdentityUserRole>, IQueryableRoleStore<TRole>, IQueryableRoleStore<TRole, string>, IRoleStore<TRole, string>, IDisposable where TRole : IdentityRole, new()
    {
        public RoleStore()
            : this(new IdentityCloudContext())
        {
            base.DisposeContext = true;
        }

        public RoleStore(IdentityCloudContext context)
            : base(context) { }
    }

    public class RoleStore<TRole, TKey, TUserRole> : IQueryableRoleStore<TRole, TKey>, IRoleStore<TRole, TKey>, IDisposable
        where TRole : IdentityRole<TKey,TUserRole>, new()
        where TUserRole : IdentityUserRole<TKey>, new()
    {
        private bool _disposed;
        private CloudTable _roleTable;

        public RoleStore(IdentityCloudContext<IdentityUser, IdentityRole, string, IdentityUserLogin, IdentityUserRole, IdentityUserClaim> context)
        {
            if (context == null)
            {
                throw new ArgumentNullException("context");
            }
            this.Context = context;
            _roleTable = context.RoleTable;
        }

        public async Task<bool> CreateTableIfNotExistsAsync()
        {
            return await Context.RoleTable.CreateIfNotExistsAsync();
        }

        public async virtual Task CreateAsync(TRole role)
        {
            ThrowIfDisposed();
            if (role == null)
            {
                throw new ArgumentNullException("role");
            }

            ((IGenerateKeys)role).GenerateKeys();

            await new TaskFactory().StartNew(() =>
                {
                    // Create the TableOperation that inserts the role entity.
                    TableOperation insertOperation = TableOperation.Insert(role);

                    // Execute the insert operation.
                    TableResult tresult = _roleTable.Execute(insertOperation);
                });

        }

        public async virtual Task DeleteAsync(TRole role)
        {
            ThrowIfDisposed();
            if (role == null)
            {
                throw new ArgumentNullException("role");
            }
            await new TaskFactory().StartNew(() =>
            {
                // Create the TableOperation that deletes the role entity.
                TableOperation deleteOperation = TableOperation.Delete(role);

                // Execute the insert operation.
                TableResult tresult = _roleTable.Execute(deleteOperation);
            });


        }

        public void Dispose()
        {
            this.Dispose(true);
            GC.SuppressFinalize(this);
        }

        protected virtual void Dispose(bool disposing)
        {
            if ((this.DisposeContext && disposing) && (this.Context != null))
            {
                this.Context.Dispose();
            }
            this._disposed = true;
            this._roleTable = null;
            this.Context = null;
        }

        public Task<TRole> FindByIdAsync(TKey roleId)
        {
            this.ThrowIfDisposed();

            var query = (from entity in Roles
                            where entity.RowKey == roleId.ToString()
                            && entity.PartitionKey == KeyHelper.ParsePartitionKeyIdentityRoleFromRowKey(roleId.ToString())
                            select entity).AsTableQuery();
            return Task.FromResult<TRole>(
                    _roleTable.ExecuteQuery(query).FirstOrDefault()
                );
        }

        public Task<TRole> FindByNameAsync(string roleName)
        {
            this.ThrowIfDisposed();
            var query = (from entity in Roles
                         where entity.RowKey == KeyHelper.GenerateRowKeyIdentityRole(roleName)
                         && entity.PartitionKey == KeyHelper.GeneratePartitionKeyIdentityRole(roleName)
                         select entity);

            return Task.FromResult<TRole>(_roleTable.ExecuteQuery(query.AsTableQuery()).SingleOrDefault());
        }

        private void ThrowIfDisposed()
        {
            if (this._disposed)
            {
                throw new ObjectDisposedException(base.GetType().Name);
            }
        }

        public async virtual Task UpdateAsync(TRole role)
        {
            ThrowIfDisposed();
            if (role == null)
            {
                throw new ArgumentNullException("role");
            }

           
            await new TaskFactory().StartNew(() =>
            {
                IGenerateKeys g = role as IGenerateKeys;
                if (!g.PeekRowKey().Equals(role.RowKey, StringComparison.Ordinal))
                {
                    TableBatchOperation batch = new TableBatchOperation();
                    DynamicTableEntity dRole = new DynamicTableEntity(role.PartitionKey, role.RowKey);
                    dRole.ETag = Constants.ETagWildcard;
                    dRole.Timestamp = role.Timestamp;
                    batch.Add(TableOperation.Delete(dRole));
                    g.GenerateKeys();
                    //PartitionKey has to be the same to participate in a batch transaction.
                    if (dRole.PartitionKey.Equals(role.PartitionKey))
                    {
                        batch.Add(TableOperation.Insert(role));
                    }
                    else
                    {
                        _roleTable.Execute(TableOperation.Insert(role));
                    }
                    var tresult = _roleTable.ExecuteBatch(batch);
                }
            });

        }

        public IdentityCloudContext<IdentityUser, IdentityRole, string, IdentityUserLogin, IdentityUserRole, IdentityUserClaim> Context { get; private set; }

        public bool DisposeContext { get; set; }

        public IQueryable<TRole> Roles
        {
            get
            {
                return _roleTable.CreateQuery<TRole>();
            }
        }

    }
}
